package guava;

import com.google.common.collect.Lists;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.DeadEvent;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * EventBus：基于事件的编程
 * User: Realfighter
 * Date: 2014/12/29
 * Time: 9:52
 */
public class EventBusTest {

    @Test
    public void testEventBus() {

        EventBus eventBus = new EventBus(TradeAccountEvent.class.getName());
        SimpleTradeAuditor auditor = new SimpleTradeAuditor(eventBus);
//        eventBus.register(auditor);
        eventBus.post(new TradeAccountEvent(new TradeAccount(), 1.0, new Date(), TradeType.SELL));
    }

    public class TradeAccountEvent {
        private double amount;
        private Date tradeExecutionTime;
        private TradeType tradeType;
        private TradeAccount tradeAccount;

        public TradeAccountEvent(TradeAccount account, double amount,
                                 Date tradeExecutionTime, TradeType tradeType) {
            checkArgument(amount > 0.0, "Trade can't be less than zero");
            this.amount = amount;
            this.tradeExecutionTime =
                    checkNotNull(tradeExecutionTime, "ExecutionTime can't be null");
            this.tradeAccount = checkNotNull(account, "Account can't be null ");
            this.tradeType = checkNotNull(tradeType, "TradeType can't be null ");
        }

        //Details left out for clarity
    }

    @Component
    public class SimpleTradeAuditor {
        private List<TradeAccountEvent> tradeEvents =
                Lists.newArrayList();

        @Autowired
        public SimpleTradeAuditor(EventBus eventBus) {
            eventBus.register(this);
        }

        @Subscribe
        public void auditTrade(TradeAccountEvent tradeAccountEvent) {
            tradeEvents.add(tradeAccountEvent);
            System.out.println("Received trade " + tradeAccountEvent);
        }
    }

    class TradeAccount {
    }

    enum TradeType {
        BUY, SELL
    }

    @Component
    public class SimpleTradeExecutor {
        private EventBus eventBus;

        @Autowired
        public SimpleTradeExecutor(EventBus eventBus) {
            this.eventBus = eventBus;
        }

        public void executeTrade(TradeAccount tradeAccount, double
                amount, TradeType tradeType) {
            TradeAccountEvent tradeAccountEvent =
                    processTrade(tradeAccount, amount, tradeType);
            eventBus.post(tradeAccountEvent);
        }

        private TradeAccountEvent processTrade(TradeAccount
                                                       tradeAccount, double amount, TradeType tradeType) {
            Date executionTime = new Date();
            String message = String.format("Processed trade for %s of amount %n type %s @%s", tradeAccount, amount, tradeType, executionTime);
            TradeAccountEvent tradeAccountEvent = new TradeAccountEvent(tradeAccount, amount, executionTime, tradeType);
            System.out.println(message);
            return tradeAccountEvent;
        }
    }

    public class SellEvent extends TradeAccountEvent {
        public SellEvent(TradeAccount tradeAccount, double amount, Date
                tradExecutionTime) {
            super(tradeAccount, amount, tradExecutionTime, TradeType.SELL);
        }
    }

    public class BuyEvent extends TradeAccountEvent {
        public BuyEvent(TradeAccount tradeAccount, double amount, Date
                tradExecutionTime) {
            super(tradeAccount, amount, tradExecutionTime, TradeType.BUY);
        }
    }

    public class TradeSellAuditor {
        private List<SellEvent> sellEvents = Lists.newArrayList();

        public TradeSellAuditor(EventBus eventBus) {
            eventBus.register(this);
        }

        @Subscribe
        public void auditSell(SellEvent sellEvent) {
            sellEvents.add(sellEvent);
            System.out.println("Received SellEvent " + sellEvent);
        }

        public List<SellEvent> getSellEvents() {
            return sellEvents;
        }
    }

    public class TradeBuyAuditor {
        private List<BuyEvent> buyEvents = Lists.newArrayList();

        public TradeBuyAuditor(EventBus eventBus) {
            eventBus.register(this);
        }

        @Subscribe
        public void auditBuy(BuyEvent buyEvent) {
            buyEvents.add(buyEvent);
            System.out.println("Received TradeBuyEvent " + buyEvent);
        }

        public List<BuyEvent> getBuyEvents() {
            return buyEvents;
        }
    }

    public class BuySellTradeExecutor {
        private EventBus eventBus;

        public BuySellTradeExecutor(EventBus eventBus) {
            this.eventBus = eventBus;
        }

        public void executeTrade(TradeAccount tradeAccount, double
                amount, TradeType tradeType) {
            TradeAccountEvent tradeAccountEvent =
                    processTrade(tradeAccount, amount, tradeType);
            eventBus.post(tradeAccountEvent);
        }

        private TradeAccountEvent processTrade(TradeAccount
                                                       tradeAccount, double amount, TradeType tradeType) {
            Date executionTime = new Date();
            String message = String.format("Processed trade for %s of amount %n type %s @%s", tradeAccount, amount, tradeType, executionTime);
            TradeAccountEvent tradeAccountEvent;
            if (tradeType.equals(TradeType.BUY)) {
                tradeAccountEvent = new BuyEvent(tradeAccount, amount,
                        executionTime);
            } else {
                tradeAccountEvent = new SellEvent(tradeAccount,
                        amount, executionTime);
            }
            System.out.println(message);
            return tradeAccountEvent;
        }

        public class AllTradesAuditor {
            private List<BuyEvent> buyEvents = Lists.newArrayList();
            private List<SellEvent> sellEvents = Lists.newArrayList();

            public AllTradesAuditor(EventBus eventBus) {
                eventBus.register(this);
            }

            @Subscribe
            public void auditSell(SellEvent sellEvent) {
                sellEvents.add(sellEvent);
                System.out.println("Received TradeSellEvent " + sellEvent);
            }

            @Subscribe
            public void auditBuy(BuyEvent buyEvent) {
                buyEvents.add(buyEvent);
                System.out.println("Received TradeBuyEvent " + buyEvent);
            }
        }

        public void unregister() {
            this.eventBus.unregister(this);
        }
    }

    private ExecutorService executorService;

    AsyncEventBus asyncEventBus = new AsyncEventBus(executorService);

    public class DeadEventSubscriber {

        public DeadEventSubscriber(EventBus eventBus) {
            eventBus.register(this);
        }

        @Subscribe
        public void handleUnsubscribedEvent(DeadEvent deadEvent) {
            System.out.println("No subscribers for " + deadEvent.getEvent());
        }
    }

    @Configuration
    @ComponentScan(basePackages = {"guava"})
    public class EventBusConfig {
        @Bean
        public EventBus eventBus() {
            return new EventBus();
        }
    }

}
